package com.yuandian.smart.analysis.redis;

import cn.hutool.core.util.IdUtil;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.util.StopWatch;

import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;

/**
 * 说明：
 *
 * @Author zfsn
 * @Date 2023/4/11 10:09
 */
@SpringBootTest
public class Demo1 {

	@Autowired
	private RedisTemplate<String, String> redisTemplate;

	private int num;

	@Test
	public void fun1() throws InterruptedException {
		int size = 20;
		CountDownLatch countDownLatch = new CountDownLatch(size);
		for (int i = 0; i < size; i++) {
			new Thread(() -> {
				try {
					for (int j = 0; j < 1000; j++) {
						this.num++;
					}
				} finally {
					countDownLatch.countDown();
				}
			}).start();
		}
		countDownLatch.await();
		System.out.println("num: " + this.num);
	}

	@Test
	public void fun2() throws InterruptedException {
		String lockKey = "distributed-key";
		String lockValue = "distributed-value";
		int size = 20;
		CountDownLatch countDownLatch = new CountDownLatch(size);
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		for (int i = 0; i < size; i++) {
			new Thread(() -> {
				try {
					// 自旋获取分布式锁
					while (true) {
						// 执行临界区代码时先通过setnx加锁
						if (Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue))) {
							for (int j = 0; j < 1000; j++) {
								this.num++;
							}
							break;
						}
						// 获取锁失败时等一段时间再进行自旋
						TimeUnit.MILLISECONDS.sleep(100);
					}
				} catch (InterruptedException e) {
					e.printStackTrace();
				} finally {
					// 释放锁资源
					redisTemplate.delete(lockKey);
					countDownLatch.countDown();
				}
			}).start();
		}
		countDownLatch.await();
		stopWatch.stop();
		System.out.println("num: " + this.num + ", time: " + stopWatch.getTotalTimeMillis() + "ms");
	}

	/*public void distributedSynchronized(String lockKey, String lockValue, Consumer<String> consumer) {
		try {
			// 自旋获取分布式锁
			while (true) {
				// 加锁
				if (Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue))) {
					consumer.accept(lockKey);
					break;
				}
				// 获取锁失败时等一段时间再进行自旋
				TimeUnit.MILLISECONDS.sleep(1000);
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			// 释放锁
			redisTemplate.delete(lockKey);
		}
	}*/

	/*public void distributedSynchronized(String lockKey, String lockValue, Consumer<String> consumer) {
		try {
			// 自旋获取分布式锁
			while (true) {
				// 带有过期时间加锁
				if (Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(
						lockKey, lockValue, 10, TimeUnit.SECONDS))) {
					consumer.accept(lockKey);
					break;
				}
				// 获取锁失败时等一段时间再进行自旋
				TimeUnit.MILLISECONDS.sleep(1000);
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			// 释放锁
			redisTemplate.delete(lockKey);
		}
	}*/

	private DefaultRedisScript<Long> lockScript;
	private DefaultRedisScript<Long> unLockScript;

	@BeforeEach
	public void init() {
		lockScript = new DefaultRedisScript<>();
		lockScript.setResultType(Long.class);
		lockScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("lua/lock.lua")));
		unLockScript = new DefaultRedisScript<>();
		unLockScript.setResultType(Long.class);
		unLockScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("lua/unlock.lua")));
	}

	/*public void distributedSynchronized(String lockKey, String lockValue, Consumer<String> consumer) {
		try {
			// 自旋获取分布式锁
			while (true) {
				// 使用lua脚本带有过期时间加锁
				if (Objects.equals(redisTemplate.execute(luaScript,
						Collections.singletonList(lockKey), lockValue, "10"), 1L)) {
					consumer.accept(lockKey);
					break;
				}
				// 获取锁失败时等一段时间再进行自旋
				TimeUnit.MILLISECONDS.sleep(1000);
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			// 释放锁
			redisTemplate.delete(lockKey);
		}
	}*/

	public void distributedSynchronized(String lockKey, Consumer<String> consumer) {
		// 生成一个唯一的value值，用于在释放锁时校验，避免误删
		String lockValue = IdUtil.fastSimpleUUID();
		List<String> lockKeys = Collections.singletonList(lockKey);
		try {
			// 自旋获取分布式锁
			while (true) {
				// 使用lua脚本带有过期时间加锁
				if (Objects.equals(redisTemplate.execute(lockScript,
						lockKeys, lockValue, "10"), 1L)) {
					consumer.accept(lockKey);
					break;
				}
				// 获取锁失败时等一段时间再进行自旋
				TimeUnit.MILLISECONDS.sleep(1000);
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			// 使用lua脚本校验唯一value值释放锁
			redisTemplate.execute(unLockScript, lockKeys, lockValue);
		}
	}

	@Test
	public void fun3() throws InterruptedException {
		String lockKey = "distributed-key";
		int size = 20;
		CountDownLatch countDownLatch = new CountDownLatch(size);
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		for (int i = 0; i < size; i++) {
			new Thread(() -> {
				try {
					distributedSynchronized(lockKey, key -> {
						for (int j = 0; j < 1000; j++) {
							this.num++;
						}
					});
				} finally {
					countDownLatch.countDown();
				}
			}).start();
		}
		countDownLatch.await();
		stopWatch.stop();
		System.out.println("num: " + this.num + ", time: " + stopWatch.getTotalTimeMillis() + "ms");
	}

	@Test
	public void fun4() throws InterruptedException {
		String lockAKey = "distributed-key-A";
		String lockBKey = "distributed-key-B";

		CountDownLatch countDownLatch = new CountDownLatch(2);

		new Thread(() -> {
			distributedSynchronized(lockAKey, keyA -> {
				System.out.println(Thread.currentThread().getName() + "获取锁A，执行逻辑...");
				// 睡眠1秒保证线程B能获取到锁B
				try {
					TimeUnit.SECONDS.sleep(1);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				// 线程A在持有锁A的情况下获取锁B，但锁B被线程B持有，会无限自旋导致死锁
				distributedSynchronized(lockBKey, keyB -> {
					System.out.println(Thread.currentThread().getName() + "获取锁B，执行逻辑...");
				});
				countDownLatch.countDown();
			});
		}, "线程A").start();

		new Thread(() -> {
			distributedSynchronized(lockBKey, keyB -> {
				System.out.println(Thread.currentThread().getName() + "获取锁B，执行逻辑...");
				// 睡眠1秒保证线程A能获取到锁A
				try {
					TimeUnit.SECONDS.sleep(1);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				// 线程B在持有锁B的情况下获取锁A，但锁A被线程A持有，会无限自旋导致死锁
				distributedSynchronized(lockAKey, keyA -> {
					System.out.println(Thread.currentThread().getName() + "获取锁A，执行逻辑...");
				});
				countDownLatch.countDown();
			});
		}, "线程B").start();

		countDownLatch.await();
	}
}
